using System;
using System.Collections.Generic;
using System.Linq;
using Locator.GeoLocation.Services;
using Microsoft.Practices.CompositeWeb;
using Locator.GeoLocation.BusinessEntities;
using System.Threading;
using Microsoft.Practices.CompositeWeb.Interfaces;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.CompositeWeb.Utility;
using System.Web;
using DataAccess;
using DataAccess.BusinessEntities;
using Microsoft.Practices.ObjectBuilder;

namespace Locator.Stores
{
    public class StoresController : IStoresController
    {
        private readonly IGeoLocationServiceAgent geocodeServiceAgent;
        private readonly TraceManager traceManager;
        private readonly LogWriter logWriter;
        private bool disposed;
        private readonly IUnitOfWork unitOfWork;

        public StoresController
            (
            [ServiceDependency] IGeoLocationServiceAgent geocodeServiceAgent,
            [CreateNew] IUnitOfWork unitOfWork
            )
        {
            this.geocodeServiceAgent = geocodeServiceAgent;
            this.logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
            this.traceManager = new TraceManager(this.logWriter);
            this.unitOfWork = unitOfWork;
        }

        public List<Store> FindStoresInCity(string cityName)
        {
            List<Store> result = new List<Store>();
            var stores = this.unitOfWork.Stores.FindWhere(s => String.Compare(s.City, cityName, true) == 0);
            result = stores.ToList<Store>();
            //var stores = this.unitOfWork.Stores.FindWhere(store => String.Compare(store.City, cityName, true) == 0);
            //result = stores.ToList<Store>();
            var userStoreNumbers = from store in GetStoresForCurrentUser() select store.Id;
            foreach (Store Store in result)
            {
                if (userStoreNumbers.Contains(Store.Id))
                {
                    Store.UsedByUser = true;
                }
            }

            return result;
        }

        public List<string> AllCities()
        {
            List<string> result = new List<string>();
            var cities = from store in this.unitOfWork.Stores.FindAll() select store.City;
            result = cities.Distinct<string>().ToList();
            return result;
        }

        public List<Store> FindStoresByPostalCode(string postalCode, int radiusInKm)
        {
            List<Store> result = new List<Store>();
            var location = this.geocodeServiceAgent.GeoLocate(new Address() { PostalCode = postalCode });
            if (location.Latitude.HasValue && location.Longitude.HasValue)
            {
                result.AddRange(FindStoresByLatitudeAndLongitude(location.Latitude.Value, location.Longitude.Value, radiusInKm));
            }

            var userStoreNumbers = from store in GetStoresForCurrentUser() select store.Id;
            foreach (Store Store in result)
            {
                if (userStoreNumbers.Contains(Store.Id))
                {
                    Store.UsedByUser = true;
                }
            }

            return result;
        }

        public List<Store> FindStoresByLatitudeAndLongitude(decimal latitude, decimal longitude, int radiusInKm)
        {
            List<Store> result = new List<Store>();
            SortedDictionary<double, Store> sortedStores = new SortedDictionary<double, Store>();
            var matchingStores = this.unitOfWork.Stores.FindAll();
            foreach (var store in matchingStores)
            {
                double distance = MapMath.CalculateDistanceBetweenInKm((double)latitude, (double)longitude, (double)store.Latitude, (double)store.Longitude);
                if (distance <= radiusInKm && !sortedStores.ContainsKey(distance) /*&& !sortedStores.ContainsKey(dis)*/)
                {
                    store.Distance = distance;
                    sortedStores.Add(distance, store);
                }
            }

            result.AddRange(sortedStores.Values.ToList());

            return result;
        }

        public List<Store> FindStoresByPersonalLocation(int personalLocationId, int radiusInKm)
        {
            List<Store> result = new List<Store>();
            var personalLocation = this.unitOfWork.PersonalLocations.FindById(personalLocationId);
            if (personalLocation != null)
            {
                var stores = this.FindStoresByLatitudeAndLongitude(personalLocation.Latitude, personalLocation.Longitude, radiusInKm);
                result.AddRange(stores);
            }

            return result;
        }

        public List<Store> GetStoresForCurrentUser()
        {
            List<Store> result = new List<Store>();
            using (traceManager.StartTrace("General"))
            {
                var userEntity = this.GetUserEntity();
                if (userEntity.Stores != null)
                {
                    result.AddRange(userEntity.Stores);
                }
            }

            return result;
        }

        public List<PersonalLocation> GetPersonalLocationsForCurrentUser()
        {
            List<PersonalLocation> result = new List<PersonalLocation>();
            using (traceManager.StartTrace("General"))
            {
                var userEntity = this.GetUserEntity();
                result.AddRange(userEntity.PersonalLocations);
            }

            return result;
        }

        /// <summary>
        /// Gets the user entity.
        /// </summary>
        /// <param name="entities">The entities.</param>
        /// <returns></returns>
        private User GetUserEntity()
        {
            string userName = Thread.CurrentPrincipal.Identity.Name;
            User result = null;

            var userEntities = this.unitOfWork.Users.FindWhere(u => String.Compare(u.Username, userName, true) == 0).Include("Stores").Include("PersonalLocations");
            if (userEntities.Count() == 0)
            {
                User newUser = new User() { Username = userName };
                this.unitOfWork.Users.Add(newUser);
                this.unitOfWork.Commit();
                newUser = this.unitOfWork.Users.FindWhere(u => u.Username == userName).Include("Stores").Include("PersonalLocations").First();
                result = newUser;
                //                throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Couldn't find a user with user name {0} and ID {1}", userName, guid));
            }
            else
            {
                result = userEntities.First();
            }

            return result;
        }

        public List<Store> FindStoresNotUsedByCurrentUser()
        {
            List<Store> result = new List<Store>();
            User user = GetUserEntity();
            List<int> usersStoreIds = new List<int>();
            foreach (var store in user.Stores)
            {
                usersStoreIds.Add(store.Id);
            }

            var nonUserStores = this.unitOfWork.Stores.FindWhere(s => !usersStoreIds.Contains(s.Id));
            result.AddRange(nonUserStores);

            return result;
        }

        public void AddStoreForCurrentUser(int storeNumber)
        {
            User user = this.GetUserEntity();
            user.Stores.Add(this.unitOfWork.Stores.FindById(storeNumber));
            this.unitOfWork.Commit();
        }

        public void DeleteStoreForCurrentUser(int storeNumber)
        {
            User user = GetUserEntity();
            user.Stores.Remove(this.unitOfWork.Stores.FindById(storeNumber));
            this.unitOfWork.Commit();
        }


        public virtual void Search()
        {
            this.RedirectTo("StoreSearch.aspx");
        }

        protected virtual void RedirectTo(string address)
        {
            Guard.ArgumentNotNullOrEmptyString(address, "address");
            IHttpContext context = new Microsoft.Practices.CompositeWeb.Web.HttpContext(HttpContext.Current);
            context.Server.Transfer(address);
        }

        #region IDisposable Members

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (this.unitOfWork != null)
                    {
                        this.unitOfWork.Dispose();
                    }

                    if (this.logWriter != null)
                    {
                        this.logWriter.Dispose();
                    }
                }
            }

            this.disposed = true;
        }
        #endregion

        ~StoresController()
        {
            this.Dispose(false);
        }
    }
}
